import numpy as np

def randomFrag(idxRan, M, NRan):
    '''
    随机生成索引段，返回一个列表，列表中的每一项都是一个包含两个元素的列表。
    :param idxRan: list,包含两个索引，是随机数生成的范围限定
    :param M: 表示要生成多少个列表项
    :param NRan: list,包含两个索引，表示一个索引段的长度的限定范围
    :return: list,如[[45,90],[1,65],[123,192],...]
    '''
    result = []
    for i in range(M):
        N = np.random.randint(*NRan)
        loc = np.random.randint(idxRan[0], idxRan[1] - N)
        result.append([loc, loc + N])
    return result

def waveTrough(wave,loc,T=1100):
    '''
    返回loc索引附近的波谷
    :param wave: numpy一维数组，波数据
    :param loc: 索引
    :param T: 周期
    :return: 波谷处的索引
    '''
    begin=loc-T//2
    begin=0 if begin<0 else begin
    end=begin+T
    end=len(wave) if end>len(wave) else end
    idx=begin+wave[begin:end].argmin()
    return idx

def randomWaveFrag(wave, idxRan, M, NRan):
    '''
    根据wave（符合周期性的波数据）随机生成索引段，取的段刚好是多个完整的周期，返回一个列表，列表中的每一项都是一个包含两个元素的列表。
    :param idxRan: list,包含两个索引，是随机数生成的范围限定
    :param M: 表示要生成多少个列表项
    :param NRan: list,包含两个索引，表示一个索引段的长度的限定范围
    :return: list,如[[45,90],[1,65],[123,192],...]
    '''
    result = []
    i=0
    while i<M:
        N = np.random.randint(*NRan)
        loc = np.random.randint(idxRan[0], idxRan[1] - N)
        loc1=waveTrough(wave,loc)
        end=loc1 + 3 * N
        end=idxRan[1] if end>=idxRan[1] else end
        loc = np.random.randint(loc1, end)
        loc2=waveTrough(wave,loc)
        if loc1>=loc2:
            continue
        i+=1
        result.append([loc1, loc2])
    return result


def scaleOut(data, ran, fac):
    """[如果data的最小值与下限的距离比最大值与上限的距离小，则向超越下限缩放]

    Args:
        data ([numpy array]): [一维数组]
        ran ([list]): [上下限范围]
        fac ([flot]): [缩放系数，如0.1表示超出上限或者下线的10%（相对于上下限范围）]

    Returns:
        [numpy array]: [缩放得到的数据]
    """
    min = np.nanmin(data)
    max = np.nanmax(data)
    r = (ran[1] - ran[0]) * fac
    if min - ran[0] < ran[1] - max:
        scale = ((ran[0] - r) - max) / (min - max)
        return (data - max) * scale + max
    else:
        scale = ((ran[1] + r) - min) / (max - min)
        return (data - min) * scale + min

def randomSeam(data,M,NRan,errorRan,warnRan,errorProb=0.3,warnProb=0.6,noise=False):
	"""[从data中随机取多段数据进行缝合，缝合时后一段数据会偏移到前一段数据的尾部]

	Args:
		data ([numpy array]): [一维数组，用于缝合的参考数据]
		M ([int]): [决定取多少段数据进行缝合]
		NRan ([list]): [列表包含两个整型数，表示每一段数据的随机长度范围]
		errorRan ([list]): [列表包含两个数，表示错误的范围]
		warnRan ([type]): [列表包含两个数，表示预警的范围]
		errorProb (float, optional): [模拟错误的概率]. Defaults to 0.2.
		warnProb (float, optional): [模拟预警的概率]. Defaults to 0.5.
		noise (bool, optional): [是否添加噪声]. Defaults to False.

	Returns:
		[numpy array]: [缝合得到的数据]
	"""
	errorDiff=errorRan[1]-errorRan[0]
	warnDiff=warnRan[1]-warnRan[0]
	idxRan=[0,data.shape[0]]
	idxFrags=randomFrag(idxRan,M,NRan)
	lastTail=data[idxFrags[0][0]:idxFrags[0][1]][-1]
	seamData=np.array([])
	for frag in idxFrags:
		sub=data[frag[0]:frag[1]].copy()
		head=sub[0]
		diff=head-lastTail
		sub-=diff
		max=np.nanmax(sub)
		min=np.nanmin(sub)
		if noise:
			r=max-min
			sub+=np.random.uniform(-0.05,0.05,size=sub.shape)*r
		seamData=np.append(seamData,sub)
		lastTail=sub[-1]
	seamData=(seamData-np.nanmin(seamData))/(np.nanmax(seamData)-np.nanmin(seamData))#归一化
	seamData=seamData*errorDiff+errorRan[0]#参照errorRan反归一化

	error8=errorDiff*0.8+errorRan[0]
	error2=errorDiff*0.2+errorRan[0]
	warn8=warnDiff*0.8+warnRan[0]
	warn2=warnDiff*0.2+warnRan[0]
	left=0
	e=errorDiff*0.05
	w=warnDiff*0.05
	for frag in idxFrags:
		right=left+frag[1]-frag[0]
		sub=seamData[left:right]
		min=np.nanmin(sub)
		max=np.nanmax(sub)
		diff=max-min
		rand=np.random.random()
		#除了满足概率条件，还需实现满足这一段的数据接近于错误限制且这一段的数据有明显起伏，避免中间段平缓突然被模拟为错误
		if rand<errorProb and diff>e and (min<error2 or max>error8):
			sub=scaleOut(sub,errorRan,np.random.uniform(0.05,0.2))
			seamData[left:right]=sub
		elif rand<warnProb and diff>w and (min<warn2 or max>warn8):
			sub=scaleOut(sub,warnRan,np.random.uniform(0.05,0.2))
			seamData[left:right]=sub
		else:
			pass
		left=right
	return seamData

def randomSeam2(data, M, NRan, thresh, errorProb=0.3, warnProb=0.6, noise=False,waveColIdx=0):
    """[从data中随机取多段数据进行缝合，缝合时后一段数据会偏移到前一段数据的尾部]

    Args:
        data ([numpy array]): [包含多列数据，用于缝合的参考数据]
        M ([int]): [决定取多少段数据进行缝合]
        NRan ([list]): [列表包含两个整型数，表示每一段数据的随机长度范围]
        thresh ([dict]): 阈值配置项
        errorProb (float, optional): [模拟错误的概率]. Defaults to 0.2.
        warnProb (float, optional): [模拟预警的概率]. Defaults to 0.5.
        noise (bool, optional): [是否添加噪声]. Defaults to False.

    Returns:
        [numpy array]: [缝合得到的数据]
    """
    idxRan = [0, data.shape[0]]
    idxFrags = randomWaveFrag(data[:,waveColIdx],idxRan, M, NRan)
    col = data.shape[1]
    seamData=[]
    for i in range(col):
        colData = data[:, i]
        errorRan = thresh[i]['error']
        errorDiff = errorRan[1] - errorRan[0]
        seamColData = np.array([])
        lastTail = colData[idxFrags[0][0]:idxFrags[0][1]][-1]
        for frag in idxFrags:
            sub = colData[frag[0]:frag[1]].copy()#此处必须copy，否则后面的代码会修改原数组
            head = sub[0]
            diff = head - lastTail
            sub -= diff
            max = np.nanmax(sub)
            min = np.nanmin(sub)
            if noise:
                r = max - min
                sub += np.random.uniform(-0.05, 0.05, size=sub.shape) * r
            seamColData = np.append(seamColData, sub)
            if not (np.isnan(max) or np.isnan(min)):
                lastTail = sub[-1]
        seamColData = (seamColData - np.nanmin(seamColData)) / (np.nanmax(seamColData) - np.nanmin(seamColData))  # 归一化
        seamColData = seamColData * errorDiff + errorRan[0]  # 参照errorRan反归一化
        seamData = np.append(seamData,seamColData)
    seamData.resize((col,seamData.shape[0]//col))
    seamData = seamData.transpose()
    for i in range(col):
        colData = data[:, i]
        errorRan = thresh[i]['error']
        errorDiff = errorRan[1] - errorRan[0]
        warnRan = thresh[i]['warn']
        warnDiff = warnRan[1] - warnRan[0]
        error8 = errorDiff * 0.8 + errorRan[0]
        error2 = errorDiff * 0.2 + errorRan[0]
        warn8 = warnDiff * 0.8 + warnRan[0]
        warn2 = warnDiff * 0.2 + warnRan[0]
        left = 0
        e = errorDiff * 0.05
        w = warnDiff * 0.05
        for frag in idxFrags:
            right = left + frag[1] - frag[0]
            sub = seamData[:,i][left:right]
            min = np.nanmin(sub)
            max = np.nanmax(sub)
            diff = max - min
            rand = np.random.random()
            # 除了满足概率条件，还需实现满足这一段的数据接近于错误限制且这一段的数据有明显起伏，避免中间段平缓突然被模拟为错误
            if rand < errorProb and diff > e and (min < error2 or max > error8):
                sub = scaleOut(sub, errorRan, np.random.uniform(0.05, 0.15))
                seamData[:,i][left:right] = sub
            elif rand < warnProb and diff > w and (min < warn2 or max > warn8):
                sub = scaleOut(sub, warnRan, np.random.uniform(0.05, 0.15))
                seamData[:,i][left:right] = sub
            else:
                pass
            left = right
    return seamData

if __name__ == "__main__":
    import json
    import pandas as pd
    import matplotlib.pyplot as plt
    np.random.seed(1103)
    with open("config.json",'r',encoding='utf8') as fi:
        config=json.load(fi)
        fi.close()
    filePath = '../data/一号泵.csv'
    data = pd.read_csv(filePath, encoding="utf-8")
    fields=['A相电压', 'B相电压', 'C相电压','瞬时流量']
    thresh=[config["thresh"][field] for field in fields]
    data = data[fields]#data[PRESET_FIELDS]
    data = data.values
    simData = randomSeam2(data, 50, [400, 600],thresh,waveColIdx=0)

    x=[i for i in range(simData.shape[0])]
    for i in range(simData.shape[1]):
        plt.figure()
        plt.plot(x,simData[:,i])
        max=thresh[i]['error'][1]
        min=thresh[i]['error'][0]
        ran=max-min
        print(ran*0.1+min,ran*0.9+min)
        plt.axhline(max)
        plt.axhline(min)
    plt.show()
    pass